using System;
using Velocity4Net.Ctx;
using Velocity4Net.Runtime.Parse.AST;
using System.IO;
using Velocity4Net.Errors;
using Velocity4Net.App.Events;
using System.Text;
using Velocity4Net.Runtime.Logging;
namespace Velocity4Net.Runtime.Directives
{

    /**
     * Pluggable directive that handles the <code>#parse()</code>
     * statement in VTL.
     *
     * <pre>
     * Notes:
     * -----
     *  1) The parsed source material can only come from somewhere in
     *    the TemplateRoot tree for security reasons. There is no way
     *    around this.  If you want to include content from elsewhere on
     *    your disk, use a link from somwhere under Template Root to that
     *    content.
     *
     *  2) There is a limited parse depth.  It is set as a property
     *    "directive.parse.max.depth = 10" by default.  This 10 deep
     *    limit is a safety feature to prevent infinite loops.
     * </pre>
     *
     * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
     * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
     * @author <a href="mailto:Christoph.Reck@dlr.de">Christoph Reck</a>
     * @version $Id: Parse.java 928253 2010-03-27 19:39:04Z nbubna $
     */
    public class Parse : InputBase
    {
        private int maxDepth;

        /**
         * Return name of this directive.
         * @return The name of this directive.
         */
        public override String Name
        {
            get
            {
                return "parse";
            }
            set { }
        }

        /**
         * Overrides the default to use "template", so that all templates
         * can use the same scope reference, whether rendered via #parse
         * or direct merge.
         */
        public override String ScopeName
        {
            get
            {
                return "template";
            }
        }

        /**
         * Return type of this directive.
         * @return The type of this directive.
         */
        public override int Type
        {
            get
            {
                return 2;
            }
        }

        /**
         * Init's the #parse directive.
         * @param rs
         * @param context
         * @param node
         * @throws TemplateInitException
         */
        public override void Init(RuntimeServices rs, InternalContextAdapter context, INode node)
        {
            base.Init(rs, context, node);

            this.maxDepth = rsvc.GetInt(RuntimeConstants.PARSE_DIRECTIVE_MAXDEPTH, 10);
        }

        /**
         *  iterates through the argument list and renders every
         *  argument that is appropriate.  Any non appropriate
         *  arguments are logged, but render() continues.
         * @param context
         * @param writer
         * @param node
         * @return True if the directive rendered successfully.
         * @throws IOException
         * @throws ResourceNotFoundException
         * @throws ParseErrorException
         * @throws MethodInvocationException
         */
        public override bool Render(InternalContextAdapter context,
                               TextWriter writer, INode node)
        {
            /*
             *  did we get an argument?
             */
            if (node.jjtGetNumChildren() == 0)
            {
                throw new VelocityException("#parse(): argument missing at " +
                                            Log.formatFileString(this));
            }

            /*
             *  does it have a value?  If you have a null reference, then no.
             */
            Object value = node.jjtGetChild(0).GetValue(context);
            if (value == null && rsvc.getLog().isDebugEnabled())
            {
                rsvc.getLog().debug("#parse(): null argument at " +
                                    Log.formatFileString(this));
            }

            /*
             *  get the path
             */
            String sourcearg = value == null ? null : value.ToString();

            /*
             *  check to see if the argument will be changed by the event cartridge
             */
            String arg = EventHandlerUtil.includeEvent(rsvc, context, sourcearg, context.CurrentTemplateName, Name);

            /*
             *   a null return value from the event cartridge indicates we should not
             *   input a resource.
             */
            if (arg == null)
            {
                // abort early, but still consider it a successful rendering
                return true;
            }


            if (maxDepth > 0)
            {
                /* 
                 * see if we have exceeded the configured depth.
                 */
                Object[] templateStack = context.TemplateNameStack;
                if (templateStack.Length >= maxDepth)
                {
                    StringBuilder path = new StringBuilder();
                    for (int i = 0; i < templateStack.Length; ++i)
                    {
                        path.Append(" > " + templateStack[i]);
                    }
                    rsvc.getLog().error("Max recursion depth reached (" +
                                        templateStack.Length + ')' + " File stack:" +
                                        path);
                    return false;
                }
            }

            /*
             *  now use the Runtime resource loader to get the template
             */

            Template t = null;

            try
            {
                t = rsvc.GetTemplate(arg, getInputEncoding(context));
            }
            catch (ResourceNotFoundException rnfe)
            {
                /*
                 * the arg wasn't found.  Note it and throw
                 */
                rsvc.getLog().error("#parse(): cannot find template '" + arg +
                                    "', called at " + Log.formatFileString(this));
                throw rnfe;
            }
            catch (ParseErrorException pee)
            {
                /*
                 * the arg was found, but didn't parse - syntax error
                 *  note it and throw
                 */
                rsvc.getLog().error("#parse(): syntax error in #parse()-ed template '"
                                    + arg + "', called at " + Log.formatFileString(this));
                throw pee;
            }
            /**
             * pass through application level runtime exceptions
             */
            catch (ApplicationException e)
            {
                rsvc.getLog().error("Exception rendering #parse(" + arg + ") at " +
                                    Log.formatFileString(this));
                throw e;
            }
            catch (System.Exception e)
            {
                String msg = "Exception rendering #parse(" + arg + ") at " +
                             Log.formatFileString(this);
                rsvc.getLog().error(msg, e);
                throw new VelocityException(msg, e);
            }

            /**
             * Add the template name to the macro libraries list
             */
            //IList macroLibraries = context.MacroLibraries;

            /**
             * if macroLibraries are not set create a new one
             */
            //if (macroLibraries == null)
            //{
            //    macroLibraries = new ArrayList();
            //}

            //context.setMacroLibraries(macroLibraries);

            //macroLibraries.add(arg);

            /*
             *  and render it
             */
            try
            {
                preRender(context);
                context.PushCurrentTemplateName(arg);

                ((SimpleNode)t.Data).Render(context, writer);
            }
            catch (StopCommand stop)
            {
                if (!stop.isFor(this))
                {
                    throw stop;
                }
            }
            /**
             * pass through application level runtime exceptions
             */
            catch (ApplicationException e)
            {
                /**
                 * Log #parse errors so the user can track which file called which.
                 */
                rsvc.getLog().error("Exception rendering #parse(" + arg + ") at " +
                                    Log.formatFileString(this));
                throw e;
            }
            catch (System.Exception e)
            {
                String msg = "Exception rendering #parse(" + arg + ") at " +
                             Log.formatFileString(this);
                rsvc.getLog().error(msg, e);
                throw new VelocityException(msg, e);
            }
            finally
            {
                context.PopCurrentTemplateName();
                postRender(context);
            }

            /*
             *    note - a blocked input is still a successful operation as this is
             *    expected behavior.
             */

            return true;
        }

    }

}
